package br.pucrs.lognplay.repository.mapper.jpa;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import br.pucrs.lognplay.entity.GenericEntity;
import br.pucrs.lognplay.repository.GenericRepository;
import br.pucrs.lognplay.repository.RepositoryException;

public abstract class GenericMapper<E extends GenericEntity<?>, I extends Serializable> implements GenericRepository<E, I> {

	private Class<E> entityClass;

	private EntityManager entityManager;

	public GenericMapper() {

	}

	public GenericMapper(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	@SuppressWarnings("unchecked")
	protected Class<E> getEntityClass() {
		if (entityClass == null) {
			ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
			entityClass = (Class<E>) type.getActualTypeArguments()[0];
		}

		return entityClass;
	}

	protected EntityManager getEntityManager() {
		return entityManager;
	}

	public E merge(E entity) throws RepositoryException {
		try {
			return getEntityManager().merge(entity);
		} catch (Exception e) {
			throw new RepositoryException("Error on merge an entity", e);
		}
	}

	public void remove(E entity) throws RepositoryException {
		try {
			getEntityManager().remove(entity);
		} catch (Exception e) {
			throw new RepositoryException("Error on remove an entity", e);
		}
	}

	public void remove(I id) throws RepositoryException {
		try {
			E entity = getEntityManager().getReference(getEntityClass(), id);
			getEntityManager().remove(entity);
		} catch (Exception e) {
			throw new RepositoryException("Error on remove an entity", e);
		}
	}

	public E get(I id) throws RepositoryException {
		try {
			return getEntityManager().find(getEntityClass(), id);
		} catch (Exception e) {
			throw new RepositoryException("Error on get an entity", e);
		}
	}

	public List<E> listAll() throws RepositoryException {
		try {
			CriteriaBuilder b = getEntityManager().getCriteriaBuilder();
			CriteriaQuery<E> q = b.createQuery(getEntityClass());
			q.select(q.from(getEntityClass()));
			TypedQuery<E> typedQuery = getEntityManager().createQuery(q);
			return typedQuery.getResultList();
		} catch (Exception e) {
			throw new RepositoryException("Error on list entities", e);
		}
	}

	public Long size() throws RepositoryException {
		try {
			CriteriaBuilder b = getEntityManager().getCriteriaBuilder();
			CriteriaQuery<Long> q = b.createQuery(Long.class);
			q.select(b.count(q.from(getEntityClass())));
			TypedQuery<Long> typedQuery = getEntityManager().createQuery(q);
			return typedQuery.getSingleResult();
		} catch (Exception e) {
			throw new RepositoryException("Error on count entities", e);
		}
	}
}
